#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <fstream>
#include "capi2032.h"
#include "simplestack.h"
#include "capiobj.h"

using namespace std;

//==========================================================================>>
// Helper log output function
//==========================================================================>>

void logout( ostream *pos, const char *szFormat, ... )
{
    if ( !pos )
        return;

    va_list args;
    char szBuf[8192];
    long len;

    va_start( args, szFormat );
    len = _vsnprintf( szBuf, sizeof(szBuf), szFormat, args );
    va_end(args);

    if ( len < 0 || len > sizeof(szBuf)-1 )
        len = sizeof(szBuf)-1;

    szBuf[len] = '\0';

    (*pos) << szBuf << flush;
}

/*==========================================================================>>
WHAT
    This class incapsultates a number of functions that serve only one purpose:
    form parameters for CAPI 2.0 messages.
USAGE
    Create an object specifying the pointer to the data area where to build the
    structure and, optionally, the size of the area (for bounds checking). Maximum
    (and default) area size is 256. Then call the corresponding functions to fill
    in the parameters. To create the "struct" parameter, embrace the contents of
    the struct with begin() and end().
<<==========================================================================*/

class CAPIParams
{
private:
    CAPIParams( CAPIParams& );
    const CAPIParams& operator=( CAPIParams& );

public:
    CAPIParams( byte *inibuf, dword inisize );
    virtual ~CAPIParams() {};
    void empty() { b(0); }          // Solely for aesthetics
    void b( byte val ) { *p++ = val; assert(p-buf<=size); }
    void w( word val ) { *(word*)p = val; p+=2; assert(p-buf<=size); }
    void d( dword val ) { *(dword*)p = val; p+=4; assert(p-buf<=size); }
    void str( char *szString );     // Makes [strlen][char][char]...
    void rawdata( const byte *data, const dword datasize );
    void begin();                   // To mark the start of a "struct" parameter
    void end();                     // To mark the end of a "struct" parameter
    const byte *pos() const { return p; }

private:
    byte *buf;
    dword size;
    byte *p;
    simplestack<byte*> marks;
};

CAPIParams::CAPIParams( byte *inibuf, dword inisize = 256 ) :
    marks(20)
{
    assert( inibuf );
    assert( inisize );
    buf = inibuf;
    size = inisize;
    p = buf;
}

void CAPIParams::begin()
{
    marks.push(p++);
}

void CAPIParams::end()
{
    byte *pmarked = marks.top();
    marks.pop();
    *pmarked = p-pmarked-1;
}

void CAPIParams::str( char *szString )
{
    dword dwStringLen = szString ? strlen(szString) : 0;
    assert( dwStringLen <= 255 );
    *p++ = (byte)dwStringLen;
    memcpy( p, szString, dwStringLen );
    p += dwStringLen;
    assert(p-buf<=size);
}

void CAPIParams::rawdata( const byte *data, const dword datasize )
{
    if ( !data || !datasize ) return;
    assert( datasize <= 255 );
    memcpy( p, data, datasize );
    p += datasize;
    assert(p-buf<=size);
}

struct CAPIRawProfile // Raw CAPI profile (see CAPI Reference, chapter 8)
{
    word wNumberOfControllers;
    word wNumberOfBChannels;

    dword dwGlobalOptions;
    dword dwB1ProtocolSupport;
    dword dwB2ProtocolSupport;
    dword dwB3ProtocolSupport;

    byte memCAPIInternalInformation[24];
    byte memManufacturerSpecificInformation[20];
};

const byte memBProtocol64[]      = { 9, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0, 0, 0 };
const byte memBProtocolFaxSend[] = { 20, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0, 0, 8, 0x00, 0x00, 0x00, 0x00, 1, 0x41, 1, 0x41, 2, 0x01, 0x00 };
const byte memBProtocolFaxRcv[]  = { 12, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0, 0, 0, 2, 0x02, 0x00 };

// memBProtocolRaw is used to establish a direct access to the Layer 2 of the D channel
// in order to manually send D-channel Layer 3 messages. Actually we use only the first
// element of the array (B1 Protocol = 1, B2 Protocol = 12), which works fine with
// Diva Server BRI-2M, but it may turn out that other boards require different settings.

const byte memBProtocolRaw[][16] = { { 15, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x00, 0, 5, 0x00, 0x00, 0x80, 0x01, 0, 0, 0 },
                                     { 15, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0, 5, 0x00, 0x00, 0x80, 0x01, 0, 0, 0 },
                                     { 15, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x00, 0, 5, 0x00, 0x00, 0x08, 0x01, 0, 0, 0 },
                                     { 15, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0, 5, 0x00, 0x00, 0x08, 0x01, 0, 0, 0 },
                                     { 14, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0, 5, 0x00, 0x00, 0x80, 0x01, 0, 0 }
};

CAPI::CAPI( unsigned long dwController, word wInfoMask, const char *szOwnNumber, bool bAutoAnswer, bool bAutoConnectResponse, dword maxbDataLen, ostream *plogstream, void (*cb)(void *, const _API_MSG& ), void *cbparam )
{
    this->dwController = dwController;
    this->wInfoMask = wInfoMask;
    this->bAutoAnswer = bAutoAnswer;
    this->bAutoConnectResponse = bAutoConnectResponse;
    this->plogstream = plogstream;
    this->cb = cb;
    this->cbparam = cbparam;
    sOwnNumber = szOwnNumber;
    bInitialized = false;
    *szManufacturer = '\0';
    *szSerialNumber = '\0';
    dwCAPIMajor = dwCAPIMinor = dwManufacturerMajor = dwManufacturerMinor = wApplId = wNumberOfControllers = 0;
    Initialize( maxbDataLen, true );
    memset( &m_msg, 0x00, sizeof(m_msg) );
}

void CAPI::Initialize( dword maxbDataLen, bool bForceReset )
{
    if ( isInitialized() && !bForceReset )
        return;

    DWORD threadId = 0;
    hTerminateEvent = 0;
    hSendingMutex = 0;
    hDataConfEvent = 0;

    CAPIRawProfile RawProfile;
    dword dwResult;

    dwResult = ::CAPI_INSTALLED();
    if ( dwResult != 0 ) {
        logout( plogstream, "CAPI_INSTALLED failed, error code = 0x%04lx\n", dwResult );
        return;
// !!!       throw XCAPIError( "CAPI_INSTALLED", dwResult );
    }

    ::CAPI_GET_MANUFACTURER( szManufacturer );

    dwResult = ::CAPI_GET_SERIAL_NUMBER( szSerialNumber );
    if ( dwResult != 0 ) {
        *szSerialNumber = 0;
        logout( plogstream, "CAPI_GET_SERIAL_NUMBER failed, error code = 0x%04lx", dwResult );
    }

    dwResult = ::CAPI_GET_VERSION( &dwCAPIMajor, &dwCAPIMinor, &dwManufacturerMajor, &dwManufacturerMinor );
    if ( dwResult != 0 ) {
        dwCAPIMajor = dwCAPIMinor = dwManufacturerMajor = dwManufacturerMinor = 0;
        logout( plogstream, "CAPI_GET_SERIAL_VERSION failed, error code = 0x%04lx", dwResult );
    }

    dwResult = ::CAPI_GET_PROFILE( &RawProfile, 0 );
    if ( dwResult != 0 ) {
        RawProfile.wNumberOfControllers = 0;
        logout( plogstream, "CAPI_GET_PROFILE(...,0) failed, error code = 0x%04lx", dwResult );
    }

    wNumberOfControllers = RawProfile.wNumberOfControllers;

    if ( isInitialized() )
    {
        // Release first, so that we don't register twice

        dwResult = ::CAPI_RELEASE( wApplId );
        bInitialized = 0;
        if ( dwResult != 0 )
            return;
// !!!            throw XCAPIError( "CAPI_RELEASE", dwResult );
    }

    dwResult = ::CAPI_REGISTER( 1024+1024*2, 2, 7, maxbDataLen, &wApplId );
    if ( dwResult != 0 ) {
        logout( plogstream, "CAPI_REGISTER failed, error code = 0x%04lx", dwResult );
        return;
// !!!        throw XCAPIError( "CAPI_REGISTER", dwResult );
    }

    maxBDataLen = maxbDataLen;
    ControllerProfiles.clear();

    for ( word i = 0; i < wNumberOfControllers; ++i )
    {
        dwResult = ::CAPI_GET_PROFILE( &RawProfile, i+1 );

        if ( dwResult != 0 ) {
            char msg[256];
            sprintf( msg, "CAPI_GET_PROFILE(..., %d) failed, error code = 0x%04lx", i+1, dwResult );
            assert( strlen(msg) < sizeof(msg) );
            logout( plogstream, msg );
            continue;
            }

        CAPIControllerProfile profile, *p = &profile;

        p->wNumberOfBChannels = RawProfile.wNumberOfBChannels;

        p->bSupportInternalController         = (RawProfile.dwGlobalOptions & 0x0001) != 0;
        p->bSupportExternalEquipment          = (RawProfile.dwGlobalOptions & 0x0002) != 0;
        p->bSupportHandset                    = (RawProfile.dwGlobalOptions & 0x0004) != 0;
        p->bSupportDTMF                       = (RawProfile.dwGlobalOptions & 0x0008) != 0;
        p->bSupportSupplementaryServices      = (RawProfile.dwGlobalOptions & 0x0010) != 0;
        p->bSupportChannelAllocation          = (RawProfile.dwGlobalOptions & 0x0020) != 0;
        p->bSupportParameterBChannelOperation = (RawProfile.dwGlobalOptions & 0x0040) != 0;
        p->bSupportLineInterconnect           = (RawProfile.dwGlobalOptions & 0x0080) != 0;

        p->bB1Support64                       = (RawProfile.dwB1ProtocolSupport & 0x0001) != 0;
        p->bB1Support64BitTransparent         = (RawProfile.dwB1ProtocolSupport & 0x0002) != 0;
        p->bB1SupportV110Asynch               = (RawProfile.dwB1ProtocolSupport & 0x0004) != 0;
        p->bB1SupportV110Synch                = (RawProfile.dwB1ProtocolSupport & 0x0008) != 0;
        p->bB1SupportT30                      = (RawProfile.dwB1ProtocolSupport & 0x0010) != 0;
        p->bB1Support64Inverted               = (RawProfile.dwB1ProtocolSupport & 0x0020) != 0;
        p->bB1Support56BitTransparent         = (RawProfile.dwB1ProtocolSupport & 0x0040) != 0;
        p->bB1SupportModemNegot               = (RawProfile.dwB1ProtocolSupport & 0x0080) != 0;
        p->bB1SupportModemAsynch              = (RawProfile.dwB1ProtocolSupport & 0x0100) != 0;
        p->bB1SupportModemSynch               = (RawProfile.dwB1ProtocolSupport & 0x0200) != 0;

        p->bB2SupportX75                      = (RawProfile.dwB2ProtocolSupport & 0x0001) != 0;
        p->bB2SupportTransparent              = (RawProfile.dwB2ProtocolSupport & 0x0002) != 0;
        p->bB2SupportSDLC                     = (RawProfile.dwB2ProtocolSupport & 0x0004) != 0;
        p->bB2SupportQ921                     = (RawProfile.dwB2ProtocolSupport & 0x0008) != 0;
        p->bB2SupportT30                      = (RawProfile.dwB2ProtocolSupport & 0x0010) != 0;
        p->bB2SupportPPP                      = (RawProfile.dwB2ProtocolSupport & 0x0020) != 0;
        p->bB2SupportTransparentIgnoreB1      = (RawProfile.dwB2ProtocolSupport & 0x0040) != 0;
        p->bB2SupportModemErrCorrCompr        = (RawProfile.dwB2ProtocolSupport & 0x0080) != 0;
        p->bB2SupportX75V42bis                = (RawProfile.dwB2ProtocolSupport & 0x0100) != 0;
        p->bB2SupportV120Asynch               = (RawProfile.dwB2ProtocolSupport & 0x0200) != 0;
        p->bB2SupportV120AsynchV42bis         = (RawProfile.dwB2ProtocolSupport & 0x0400) != 0;
        p->bB2SupportV120BitTransparent       = (RawProfile.dwB2ProtocolSupport & 0x0800) != 0;
        p->bB2SupportQ921SAPISelection        = (RawProfile.dwB2ProtocolSupport & 0x1000) != 0;

        p->bB3SupportTransparent              = (RawProfile.dwB3ProtocolSupport & 0x0001) != 0;
        p->bB3SupportT90                      = (RawProfile.dwB3ProtocolSupport & 0x0002) != 0;
        p->bB3SupportISO8208                  = (RawProfile.dwB3ProtocolSupport & 0x0004) != 0;
        p->bB3SupportX25                      = (RawProfile.dwB3ProtocolSupport & 0x0008) != 0;
        p->bB3SupportT30                      = (RawProfile.dwB3ProtocolSupport & 0x0010) != 0;
        p->bB3SupportT30Ext                   = (RawProfile.dwB3ProtocolSupport & 0x0020) != 0;
        p->bB3SupportModem                    = (RawProfile.dwB3ProtocolSupport & 0x0040) != 0;

        memcpy( p->memCAPIInternalInformation, RawProfile.memCAPIInternalInformation, 24 );
        memcpy( p->memManufacturerSpecificInformation, RawProfile.memManufacturerSpecificInformation, 20 );

        ControllerProfiles.push_back( profile );
    }

    hSendingMutex = ::CreateMutex( 0, false /* Not initial owner */, 0 );
    assert( hSendingMutex );

    hDataConfEvent = ::CreateEvent( 0, false /* Automatic reset */, false, 0 );
    assert( hDataConfEvent );

    hTerminateEvent = ::CreateEvent( 0, true /* Manual reset */, false /* Initially nonsignaled */, 0 );
    hIncomingMessageHandlingThread = ::CreateThread( 0, 0, IncomingMessageHandlingThread, (void*)this, CREATE_SUSPENDED, &threadId );
    assert( hIncomingMessageHandlingThread );

    bInitialized = true;

    ResumeThread( hIncomingMessageHandlingThread );
}

CAPI::~CAPI()
{
    if ( !hTerminateEvent || !hIncomingMessageHandlingThread )
        return;

    // Gently tell the incoming message handling thread to terminate.

    ::SetEvent( hTerminateEvent );
    ::CAPI_RELEASE( wApplId );

    // Then wait for 5 sec and terminate it anyway.

    if ( ::WaitForSingleObject( hIncomingMessageHandlingThread, 5000 ) != WAIT_OBJECT_0 )
        ::TerminateThread( hIncomingMessageHandlingThread, -1 );

    ::CloseHandle( hIncomingMessageHandlingThread );
    ::CloseHandle( hTerminateEvent );
    ::CloseHandle( hSendingMutex );
    ::CloseHandle( hDataConfEvent );
}

word CAPI::GetNumberOfControllers()
{
    CAPIRawProfile RawProfile;
    dword dwResult;

    if ( isInitialized() )
        return wNumberOfControllers;

    dwResult = CAPI_GET_PROFILE( &RawProfile, 0 );
    if ( dwResult != 0 )
        return 0;
// !!!        throw XCAPIError( "CAPI_GET_PROFILE(..., 0)", dwResult );

    return RawProfile.wNumberOfControllers;
}

/*
* Two auxiliary routines for log output
*/

const byte *CAPI::decodeNumber( const byte *p, int bCallingNumber ) const
{
    // When the number is a calling party number, an additional byte (indicating screening)
    // is placed after the Plan/Type byte

    dword i = *p++;
    if ( i == 0 )
        logout( plogstream, "-\n" );
    else {
        logout( plogstream, "Plan/type: 0x%02X, ", (dword)(*p++) );
        if ( bCallingNumber && --i > 0 )
            logout( plogstream, "Screening: 0x%02X, %s", (dword)(*p++), i > 1 ? "" : "-" );
        for ( i--; i > 0; i-- )
            logout( plogstream, "%c", *p++ );
        logout( plogstream, "\n" );
    }
    return p;
}

const byte *CAPI::dumpParameter( const byte *p ) const
{
    dword i = *p++;
    if ( i == 0 )
        logout( plogstream, "-\n" );
    else {
        for ( ; i > 0; i-- )
            logout( plogstream, "%02X ", (dword)(*p++) );
        logout( plogstream, "\n" );
    }
    return p;
}

dword CAPI::SubmitCAPIMessage( _API_MSG& msg ) const
{
    unsigned long dwResult = ::WaitForSingleObject( hSendingMutex, 5000 );

    switch ( dwResult )
    {
        case WAIT_OBJECT_0:
            break;
        case WAIT_ABANDONED:
            logout( plogstream, "Some thread has terminated without releasing the hSendingMutex mutex.\n" );
            break;
        case WAIT_TIMEOUT:
            logout( plogstream, "Something went wrong -- hSendingMutex mutex is not released in 5 seconds.\n" );
            break;
        default:
            logout( plogstream, "Problems with hSendingMutex.\n" );
            return dwResult;
    };

    SpitToLog( msg );

    dwResult = CAPI_PUT_MESSAGE( wApplId, &msg );
    if ( dwResult != 0 && dwResult != 0x1103 )
        logout( plogstream, "\n===>CAPI_PUT_MESSAGE failed, error 0x%04X.\n", dwResult );

    ReleaseMutex( hSendingMutex );
    return dwResult;
}

byte *CAPI::string2struct( byte *p, char *szString )
{
    dword dwStringLen = szString ? strlen(szString) : 0;
    assert( dwStringLen <= 255 );
    *p++ = (byte)dwStringLen;
    memcpy( p, szString, dwStringLen );
    p += dwStringLen;
    return p;
}

void CAPI::Listen( word wInfoMask, dword dwCIPMask )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.command = _LISTEN_R;
    msg.header.controller = (byte)dwController;

    msg.info.listen_req.Info_Mask = wInfoMask;
    msg.info.listen_req.CIP_Mask = dwCIPMask;
    CAPIParams s( msg.info.listen_req.structs );

    s.d(0);         // CIP Mask 2
    s.empty();      // Calling Party Number
    s.empty();      // Calling Party Subaddress

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    SubmitCAPIMessage( msg );
}

void CAPI::Connect( word wCIPValue, const char *szDA, byte cTypeAndPlan, char *szOA, byte *byteChannelIdentification )
{
    // szDA -- destination (called party) address in the form of "<number>|<subaddress>"
    // szOA -- originating (calling party) address in the form of "<number>|<subaddress>"

    char *szDN = 0;  // Called party number
    char *szDS = 0;  // Called party subaddress
    char *szON = 0;  // Calling party number
    char *szOS = 0;  // Calling party subaddress
    char *p;

    if ( szDA && (szDA[0] != '\0') ) {
        szDN = new char[strlen(szDA)+2];
        szDN[0] = cTypeAndPlan;
        strcpy( szDN+1, szDA );
    }

    if ( szOA && (szOA[0] != '\0') ) {
        szON = new char[strlen(szOA)+3];
        szON[0] = '\x01';
        szON[1] = '\x80';
        strcpy( szON+2, szOA );
    }

    if ( szDN ) {
        p = strchr( szDN+1, '|' );
        if ( p && (*(p+1)!='\0') ) {
            *p++ = '\0';
            szDS = new char[strlen(p)+2];
            strcpy( szDS+1, p );
            szDS[0] = '\xA0';
        }
    }

    if ( szON && (p=strchr(szON+2,'|')) && (*(p+1)!='\0') ) {
        *p++ = '\0';
        szOS = new char[strlen(p)+2];
        szOS[0] = '\xA0';
        strcpy( szOS+1, p );
    }

    Connect( wCIPValue, szDN, szON, szDS, szOS, byteChannelIdentification );

    delete [] szDN;
    delete [] szDS;
    delete [] szON;
    delete [] szOS;
}

void CAPI::Connect( word wCIPValue,
                    char *szCalledPartyNumber, char *szCallingPartyNumber,
                    char *szCalledPartySubaddress, char *szCallingPartySubaddress,
                    byte *byteChannelIdentification )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.command = _CONNECT_R;
    msg.header.number = 0xbb;
    msg.header.controller = (byte)dwController;
    msg.info.connect_req.CIP_Value = wCIPValue;

    CAPIParams s( msg.info.connect_req.structs );

    s.str( szCalledPartyNumber );
    s.str( szCallingPartyNumber );
    s.str( szCalledPartySubaddress );
    s.str( szCallingPartySubaddress );
    s.rawdata( memBProtocol64, *memBProtocol64+1 ); // B Protocol
    s.b(0);                                         // BC
    s.b(0);                                         // LLC
    s.b(0);                                         // HLC
    if ( byteChannelIdentification == 0 || *byteChannelIdentification == 0 )
        // We leave the "Additional Information" parameter empty if
        // no "B Channel Identification" was specified
        s.empty();
    else {
        s.begin();                     // Additional Info
            s.begin();                 // B Channel Information
                s.w(4);                // Use channel identification information element ETS 300 102-1
                s.rawdata( byteChannelIdentification, *byteChannelIdentification+1 );
            s.end();
            s.empty();                 // Keypad facility
            s.empty();                 // User-user data
            s.empty();                 // Facility Data Array
        s.end();
    }

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );

    if ( m_msg.header.number != 0x00 ) {
        logout( plogstream, "ERROR: more than one simultaneously initiated connection\n" );
        return;
    }
    else
        m_msg = msg;

    SubmitCAPIMessage( msg );
}

void CAPI::ConnectLeased( int nBchannel )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.command = _CONNECT_R;
    msg.header.number = 0xbb;
    msg.header.controller = (byte)dwController;
    msg.info.connect_req.CIP_Value = 1;

    CAPIParams s( msg.info.connect_req.structs );

    if ( nBchannel > 30 && nBchannel < 0 )
        nBchannel = 0;

    byte array[31];

    for ( int i = 0; i < 31; i++ )
        array[i] = (i!=nBchannel) ? 0 : (i!=0) ? 0xff : 0xc0;

    s.empty();                                      // Called party number
    s.empty();                                      // Calling party number
    s.empty();                                      // Called party subaddress
    s.empty();                                      // Calling party subaddress
    s.rawdata( memBProtocol64, *memBProtocol64+1 ); // B Protocol
    s.empty();                                      // BC
    s.empty();                                      // LLC
    s.empty();                                      // HLC
    s.begin();                     // Additional Info
        s.begin();                 // B Channel Information
            s.w(3);                // Use channel allocation
            s.w(1);                // Operation: DCE mode
            s.rawdata( array, sizeof(array) );
        s.end();
        s.empty();                 // Keypad facility
        s.empty();                 // User-user data
        s.empty();                 // Facility Data Array
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );

    if ( m_msg.header.number != 0x00 ) {
        logout( plogstream, "ERROR: more than one simultaneously initiated connection\n" );
        return;
    }
    else
        m_msg = msg;

    SubmitCAPIMessage( msg );
}

void Connection::DataB3Ind( const _API_MSG& msg )
{
    if ( posRecord )
    {
        byte *p = (byte*)msg.info.data_b3_ind.Data;
        byte *q = bufRecording;
        
        for ( int i = 0; i < msg.info.data_b3_ind.Data_Length; ++i )
            *q++ = flip_byte[*p++];

        posRecord->write( (const char*)bufRecording, msg.info.data_b3_ind.Data_Length );
    }

    if ( pWavDevice == 0 )
        return;

    // Beginning of audio handling code

    byte *buf = pWavDevice->GetBuffer();
    if ( buf == 0 ) {
        logout( m_capi.plogstream, "Audio stream overflow. Increase number of buffers\n" );
        return;
    }

    byte *p;
    unsigned long i;
    word *p1 = (word *)buf;

    if ( buf != 0 ) {
        for ( p=(byte*)msg.info.data_b3_ind.Data, i=0; i < msg.info.data_b3_ind.Data_Length; p++, p1++, i++ )
            *p1 = alaw_to_s16[*p];
        pWavDevice->BufferIsReady( buf, msg.info.data_b3_ind.Data_Length*2 );
    }

    // End of audio handling code
}

void Connection::Alert( byte *byteChannelIdentification )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _ALERT_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    CAPIParams s( msg.info.alert_req.structs );

    if ( byteChannelIdentification == 0 || *byteChannelIdentification == 0 )
        // We leave the "Additional Information" parameter empty in case
        // no "B Channel Identification" was specified.
        s.empty();
    else {
        s.begin();                     // Additional Info
            s.begin();                 // B Channel Information
                s.w(4);                // Use channel identification information element ETS 300 102-1
                s.rawdata( byteChannelIdentification, *byteChannelIdentification+1 );
            s.end();
            s.empty();                 // Keypad facility
            s.empty();                 // User-user data
            s.empty();                 // Facility Data Array
        s.end();
    }

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ConnectB3()
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _CONNECT_B3_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    CAPIParams s( msg.info.connect_b3_req.structs );

    s.b(0);       // NCPI

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::DisconnectB3()
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _DISCONNECT_B3_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    CAPIParams s( msg.info.disconnect_b3_req.structs );

    s.empty();       // NCPI

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ResetB3()
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _RESET_B3_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    CAPIParams s( msg.info.reset_b3_req.structs );
    
    s.empty();       // NCPI

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::Info( char *szCalledPartyNumber, byte *memAdditionalInfo )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _INFO_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    CAPIParams s( msg.info.info_req.structs );

    s.str( szCalledPartyNumber );
    if ( memAdditionalInfo )
        s.rawdata( memAdditionalInfo, *memAdditionalInfo+1 );

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::Disconnect()
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _DISCONNECT_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    CAPIParams s( msg.info.disconnect_req.structs );

    s.empty();                             // Additional Info

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void CAPI::GetSupportedServices()
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)dwController;

    msg.info.facility_req.Selector = 3;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w(0);                            // "Get supported facilities" facility
        s.empty();                         // Specific Parameters
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    SubmitCAPIMessage( msg );
}

void Connection::HoldRetrieve( bool bHold )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_req.Selector = 3;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w( bHold ? 2 : 3 );              // 2 - hold, 3 - retrieve
        s.empty();                         // Specific Parameters
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ECT_ViaCAPI( word plci )
{
 /*
 According to ETS 300 102-1, ETS 300 196-1, ETS 300 369-1, ECT is initiated
 by sending a FACILITY message (0x62) containing FACILITY information
 element (0x1C) containing invoke component with operation value 0x06, i.e.
 the following 12-byte sequence (all numbers are hexadecimal):
           62 1c 09 91 A1 06 02 01 00 02 01 06
 CAPI 2.0 offers two ways to form this sequence. One (CAPI-assisted) is to
 use FACILITY_REQ message with "ECT" (0x0006) supplementary service
 specified in the Function subparameter. The other way (direct) is to
 manually compose the FACILITY information element and use INFO_REQ message
 to send it. The high-level way is more convenient and orderly, but not all
 CAPI drivers support it.
 This function implements the CAPI-assisted ECT.
 See also CAPI::ECT_Manually -- direct ECT facility invocation.
 */
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)plci;

    msg.header.command = _FACILITY_R;
    msg.info.facility_req.Selector = 3;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w(6);                                   // "ECT" facility
        s.begin();                                // Specific Parameters
            s.d( (m_plci << 8) | m_capi.dwController );  // PLCI of a P-HELD call
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::CD_ViaCAPI( char *szToNumber )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.header.command = _FACILITY_R;
    msg.info.facility_req.Selector = 3;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w(0x000d);                              // "CD" facility
        s.begin();                                // Specific Parameters
            s.w(0);                               // Display of own address allowed
            s.begin();                            // Deflected-to Number
                s.b(0);                           // Unknown
                s.b(0);
                s.b(0);
                s.rawdata( (unsigned char*)szToNumber, strlen(szToNumber) );
            s.end();
            s.empty();                            // Deflected-to Subaddress
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ECT_Manually()
{
 /*
 This function implements ECT by manually forming the corresponding DSS1 message.
 See also ECT_ViaCAPI for a more detailed explanation
 */
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.header.command = _INFO_R;
    CAPIParams s( msg.info.info_req.structs );

    s.empty();                               // Called Party Number parameter
    s.begin();                               // Additional Info parameter
        s.empty();                           // B channel information
        s.empty();                           // Keypad facility
        s.empty();                           // User-user data
        s.begin();                           // Facility data array
            byte aFacilityIE[] = "\x1C\x09\x91\xA1\x06\x02\x01\x00\x02\x01\x06";
            s.rawdata( aFacilityIE, sizeof(aFacilityIE)-1 );
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::SS_Manually( byte byteOperation )
{
 /*
 This function invokes an arbitrary Supplementary Service by manually composing
 the corresponding FACICITY message.
 */
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.header.command = _INFO_R;
    CAPIParams s( msg.info.info_req.structs );

    s.empty();                               // Called Party Number parameter
    s.begin();                               // Additional Info parameter
        s.empty();                           // B channel information
        s.empty();                           // Keypad facility
        s.empty();                           // User-user data
        s.begin();                           // Facility data array
            byte aFacilityIE[] = "\x1C\x09\x91\xA1\x06\x02\x01\x00\x02\x01";
            s.rawdata( aFacilityIE, sizeof(aFacilityIE)-1 );
            s.b( byteOperation );
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::Facility_Manually( byte *aIE, long dwSize )
{
 /*
 This function invokes an arbitrary Supplementary Service by manually composing
 the corresponding FACICITY message.
 */
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.header.command = _INFO_R;
    CAPIParams s( msg.info.info_req.structs );

    s.empty();                               // Called Party Number parameter
    s.begin();                               // Additional Info parameter
        s.empty();                           // B channel information
        s.empty();                           // Keypad facility
        s.empty();                           // User-user data
        s.begin();                           // Facility data array
            s.rawdata( aIE, dwSize );
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void CAPI::CF_Manually( byte type, char *szMSN, char *szForwardedToNumber )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.controller = (byte)dwController;

    msg.header.command = _INFO_R;
    CAPIParams s( msg.info.info_req.structs );
    s.empty();                               // Called Party Number parameter
    s.begin();                               // Additional Info parameter
        s.empty();                           // B channel information
        s.empty();                           // Keypad facility
        s.empty();                           // User-user data
        s.begin();                           // Facility data array
            s.b(0x1c);
            s.begin();
                s.b(0x91);
                s.b(0xa1);
                s.begin();
                    s.b(0x02);
                    s.begin();
                        s.b(0);
                    s.end();
                    s.b(0x02);
                    s.begin();
                        s.b( szForwardedToNumber && *szForwardedToNumber ? 0x07 : 0x08 );  // Activate/deactivate
                    s.end();
                    s.b(0x30);
                    s.begin();
                        s.b(0x0a);
                        s.begin();
                            s.b(type);   // CFU/CFNA/CFB
                        s.end();
                        s.b(0x0a);
                        s.begin();
                            s.b(0);      // All services
                        s.end();
                        if ( szForwardedToNumber && *szForwardedToNumber ) {
                            s.b(0x30);
                            s.begin();
                                s.b(0x80);
                                s.str( szForwardedToNumber );
                            s.end();
                        }
                        s.b(0x80);
                        s.str( szMSN );
                    s.end();
                s.end();
            s.end();
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    SubmitCAPIMessage( msg );
}

void CAPI::CFInterrogate_Manually( byte type, char *szMSN )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.controller = (byte)dwController;

    msg.header.command = _INFO_R;
    CAPIParams s( msg.info.info_req.structs );

    s.empty();                               // Called Party Number parameter
    s.begin();                               // Additional Info parameter
        s.empty();                           // B channel information
        s.empty();                           // Keypad facility
        s.empty();                           // User-user data
        s.begin();                           // Facility data array
            s.b(0x1c);
            s.begin();
                s.b(0x91);
                s.b(0xa1);
                s.begin();
                    s.b(0x02);
                    s.begin();
                        s.b(0);
                    s.end();
                    s.b(0x02);
                    s.begin();
                        s.b(0x0b);           // Interrogate
                    s.end();
                    s.b(0x30);
                    s.begin();
                        s.b(0x0a);
                        s.begin();
                            s.b(type);       // CFU/CFNA/CFB
                        s.end();
                        s.b(0x0a);
                        s.begin();
                            s.b(0);          // All services
                        s.end();
                        s.b(0x80);
                        s.str( szMSN );
                    s.end();
                s.end();
            s.end();
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    SubmitCAPIMessage( msg );
}

void Connection::EctLinkIdRequest_Manually()
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.header.command = _INFO_R;
    CAPIParams s( msg.info.info_req.structs );

    s.empty();                               // Called Party Number parameter
    s.begin();                               // Additional Info parameter
        s.empty();                           // B channel information
        s.empty();                           // Keypad facility
        s.empty();                           // User-user data
        s.begin();                           // Facility data array
            s.b( 0x1C );
            s.begin();
                s.b( 0x91 );
                s.b( 0xA1 );
                s.begin();
                    s.b( 0x02 );
                    s.begin();
                        s.b( 0x00 );
                    s.end();
                    s.b( 0x06 );
                    s.begin();
                        s.b( 0x04 ); // ccitt(0) identified-organization(4)
                        s.b( 0x00 ); // etsi(0)
                        s.b( 0x82 ); // |1|  256  |
                        s.b( 0x71 ); // |0|  113  |
                        s.b( 0x01 ); // operations-and-errors(1)
                        s.b( 0x04 ); // ectLinkIdRequest-operation(4)
                    s.end();
                s.end();
            s.end();
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::StartStopDTMFListen( bool bStart )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_req.Selector = 1;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w( bStart ? 1 : 2 );           // 1 - start, 2 - stop DTMF listen on B channel data"
        s.w(0);                          // Time in ms fow one digit; default = 40ms
        s.w(0);                          // Time in ms between digits; default = 40ms
        s.empty();                       // Characters to be sent - none
        s.begin();                       // DTMF Characteristics
            s.w(0);                      // Default
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::SendDTMF( char *szDTMF )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_req.Selector = 1;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w(3);                          // Send DTMF
        s.w(0);                          // Time in ms fow one digit; default = 40ms
        s.w(0);                          // Time in ms between digits; default = 40ms
        s.str(szDTMF);                   // Characters to be sent - none
        s.begin();                       // DTMF Characteristics
            s.w(0);                      // Default
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::SuspendResume( char *szCallIdentity, bool bSuspend )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_req.Selector = 3;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w( bSuspend ? 4 : 5 );               // 4 - suspend, 5 - resume
        s.str( szCallIdentity );               // Call Identity
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::LiConnectSimple( word plci )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_req.Selector = 5;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();                    // Facility Request Parameter
        s.w(1);                   // Connect
        s.begin();                // LI Request Parameter
            s.d(0);               // We don't want any DATA_B3_* messages
            s.begin();            // LI Connect Request Participant sequence
                s.begin();        // The only participant: the peer
                    s.d( (plci << 8) | m_capi.dwController );
                    s.d( 0x03 );
                s.end();
            s.end();
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::LiDisconnectSimple( word plci )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_req.Selector = 5;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w(2);                   // Disconnect
        s.begin();                // LI Request Parameter
            s.begin();            // LI Disconnect Request Participant sequence
                s.begin();        // The only participant: the peer
                    s.d( (plci << 8) | m_capi.dwController );
                s.end();
            s.end();
        s.end();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void CAPI::LIGetSupportedServices( word plci )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.command = _FACILITY_R;
    msg.header.controller = (byte)dwController;
    msg.header.plci = (byte)plci;

    msg.info.facility_req.Selector = 5;
    CAPIParams s( msg.info.facility_req.structs );

    s.begin();
        s.w(0);               // Get supported serices
        s.empty();
    s.end();

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    SubmitCAPIMessage( msg );
}

void Connection::ConnectResp( word wXRef, byte *byteChannelIdentification )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _CONNECT_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.connect_res.Accept = 0;

    CAPIParams s( msg.info.connect_res.structs );

    s.rawdata( memBProtocol64, *memBProtocol64+1 ); // B Protocol
    s.empty();            // Connected number
    s.empty();            // Connected Subaddress
    s.empty();            // LLC

    if ( byteChannelIdentification == 0 || *byteChannelIdentification == 0 )
        // We leave the "Additional Information" parameter empty if
        // no "B Channel Identification" was specified
        s.empty();
    else {
        s.begin();                     // Additional Info
            s.begin();                 // B Channel Information
                s.w(4);                // Use channel identification information element ETS 300 102-1
                s.rawdata( byteChannelIdentification, *byteChannelIdentification+1 );
            s.end();
            s.empty();                 // Keypad facility
            s.empty();                 // User-user data
            s.empty();                 // Facility Data Array
        s.end();
    }

    msg.header.length = s.pos()-(byte*)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ConnectActiveResp( word wXRef )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _CONNECT_ACTIVE_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.header.length = msg.info.connect_a_res.structs-(byte *)&msg;
    assert( msg.header.length < sizeof(msg) );

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ConnectB3Resp( word wXRef )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _CONNECT_B3_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    msg.info.connect_b3_res.Accept = 0;
    p = msg.info.connect_b3_res.structs;
    *p++ = 0; // NCPI
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ConnectB3ActiveResp( word wXRef )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _CONNECT_B3_ACTIVE_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    p = msg.info.connect_b3_a_res.structs;
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ConnectB3T90ActiveResp( word wXRef )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _CONNECT_B3_T90_ACTIVE_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    p = msg.info.connect_b3_t90_a_res.structs;
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::DataB3Resp( word wXRef, word wDataHandle )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _DATA_B3_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    msg.info.data_b3_res.Number = wDataHandle;
    p = ((byte*)(&msg.info.data_b3_res.Number))+2;
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::InfoResp( word wXRef )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _INFO_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    p = msg.info.info_res.structs;
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::DisconnectB3Resp( word wXRef )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _DISCONNECT_B3_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    p = msg.info.disconnect_b3_res.structs;
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::ResetB3Resp( word wXRef )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _RESET_B3_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    msg.header.length = msg.info.disconnect_b3_res.structs-(byte *)&msg;
    assert( msg.header.length < sizeof(msg) );
    m_capi.SubmitCAPIMessage( msg );
}

void Connection::DisconnectResp( word wXRef )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _DISCONNECT_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    p = msg.info.disconnect_res.structs;
    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

void Connection::FacilityResp( word wXRef, word wFacilitySelector, word wFacility )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _FACILITY_I | RESPONSE;
    msg.header.number = wXRef;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    msg.info.facility_res.Selector = wFacilitySelector;
    p = msg.info.facility_res.structs;

    if ( wFacilitySelector == 0x0003 ) {
        *p++ = 3;                             // Length
        *p++ = (byte)wFacility;      // Facility (low byte)
        *p++ = ((wFacility>>8)&0x00FF);       // Facility (high byte)
        *p++ = 0;                             // Specific Parameters
    }
    else
        *p++ = 0;                             // No Parameters

    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    m_capi.SubmitCAPIMessage( msg );
}

DWORD CALLBACK CAPI::IncomingMessageHandlingThread( void *pCAPI )
{
    dword dwResult;
    struct _API_MSG *msg;

    CAPI& capi = *(CAPI*)pCAPI;

    while ( ::WaitForSingleObject( capi.hTerminateEvent, 0 ) != WAIT_OBJECT_0 )
    {
        if ( !capi.isInitialized() ) {
            Sleep(1000);
            continue;
        }

        fflush( stdout );

        dwResult = ::CAPI_WAIT_FOR_SIGNAL( capi.wApplId );
        if ( dwResult == 0x1101 ) { // Invalid application number
            logout( capi.plogstream, "\nHuh, it seems that we have terminated...\n" );
            continue;
        }
        else if ( dwResult != 0 ) {
            logout( capi.plogstream, "\n==>CAPI_WAIT_FOR_SIGNAL failed, error 0x%04X\n", dwResult );
            continue;
        }

        dwResult = ::CAPI_GET_MESSAGE( capi.wApplId, (void**)&msg );
        if ( dwResult ==  0x1101 ) { // Invalid application number
            logout( capi.plogstream, "\nHuh, it seems that we have terminated...\n" );
            continue;
        }
        else if ( dwResult ==  0x1104 ) { // Empty queue
            logout( capi.plogstream, "\nNo messages\n" );
            continue;
        }
        else if ( dwResult != 0 ) {
            logout( capi.plogstream, "\n==>CAPI_GET_MESSAGE failed, error 0x%04X\n", dwResult );
            continue;
        }

        ((CAPI*)pCAPI)->SpitToLog( *msg );
        ((CAPI*)pCAPI)->HandleIncomingMessage( *msg );
    }

    return 0;
}

void CAPI::HandleIncomingMessage( const _API_MSG& msg )
{
    word command = msg.header.command;
    word number = msg.header.number;
    byte plci = msg.header.plci;
    byte ncci = (byte)msg.header.ncci;

    const byte *p;

    boost::shared_ptr<Connection> apTempConn( IsValidPLCI( plci ) ? 0 : new Connection( *this, plci ) );
    Connection *pconn = IsValidPLCI( plci ) ? conns[plci].get() : apTempConn.get();

    switch ( command )
    {
        case _CONNECT_R | CONFIRM:
            if ( m_msg.header.number == 0x00 ) {
                logout( plogstream, "ERROR: a confirmation came for a non-initiated connection\n" );
                break;
            }
            if ( msg.info.connect_con.Info == 0 ) {
                assert( !IsValidPLCI( plci ) );
                conns.insert( ConnectionMap::value_type( plci, apTempConn ) );
                if ( m_msg.header.number == 0xdd )
                    pconn->nBChannel = 0;
            }
            memset( &m_msg, 0x00, sizeof(m_msg) );
            break;
        case _CONNECT_B3_R | CONFIRM:
            if ( msg.info.connect_b3_con.Info == 0 )
                pconn->SetNCCI( ncci );
            break;
        case _DATA_B3_R | CONFIRM:
            SetEvent( hDataConfEvent );
            break;
        case _CONNECT_I:
            assert( !IsValidPLCI( plci ) );
            conns.insert( ConnectionMap::value_type( plci, apTempConn ) );
            wLastConnectionIndXRef = number;

            if ( bAutoConnectResponse ) {
                if ( bAutoAnswer )
                    pconn->ConnectResp( number );
                else
                    pconn->Alert();
            }

            break;
        case _CONNECT_ACTIVE_I:
            pconn->ConnectActiveResp( number );
            break;
        case _CONNECT_B3_I:
            pconn->SetNCCI( ncci );
            pconn->ConnectB3Resp( number );
            break;
        case _CONNECT_B3_ACTIVE_I:
            pconn->ConnectB3ActiveResp( number );
            break;
        case _CONNECT_B3_T90_ACTIVE_I:
            pconn->ConnectB3T90ActiveResp( number );
            break;
        case _DATA_B3_I:
            pconn->DataB3Ind( msg );
            pconn->DataB3Resp( number, msg.info.data_b3_ind.Number );
            break;
        case _RESET_B3_I:
            pconn->ResetB3Resp( number );
            break;
        case _DISCONNECT_B3_I:
            pconn->DisconnectB3Resp( number );
            pconn->SetNCCI( 0 );
            break;
        case _DISCONNECT_I:
            pconn->DisconnectResp( number );
            conns.erase( plci );
            break;
        case _FACILITY_I:
            pconn->FacilityResp( number, msg.info.facility_ind.Selector, *((word *)(((byte *)msg.info.facility_ind.structs)+1)) );

            // See if a HOLD_ACK or RETRIEVE_ACK is reported to update the connection's state

            p = msg.info.facility_ind.structs;
            if ( msg.info.facility_ind.Selector == 3 && memcmp( p, "\x05\x02\x00\x02\x00\x00", 6 ) == 0 )
                pconn->m_state = Connection::stHeld;
            else if ( msg.info.facility_ind.Selector == 3 && memcmp( p, "\x05\x03\x00\x02\x00\x00", 6 ) == 0 )
                pconn->m_state = Connection::stActive;

            break;
        case _INFO_I:
            pconn->InfoResp( number );

            if ( msg.info.info_ind.Number != 0x18 )
                break;

            // Parse b channel

            p = msg.info.info_ind.structs;
            if ( *p == 1 )
                pconn->nBChannel = *(p+1) & 0x03;
            else if ( *p == 3 && *(p+2) == 0x83 )
                pconn->nBChannel = *(p+3) & 0x7f;
            else
                logout( plogstream, "Invalid B channel info coding" );

            break;
        default:
            break;
    }

    if ( command != _DATA_B3_I && command != (_DATA_B3_R | CONFIRM) )
        cb( cbparam, msg );
}

void CAPI::ConnectDChannel( word nBProtocolIndex )
{
    // nBProtocolIndex is the index of the of the "B Protocol" parameter
    // to pick from the memBProtocolRaw array

    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)wApplId;
    msg.header.command = _CONNECT_R;
    msg.header.number = 0xdd;
    msg.header.controller = (byte)dwController;

    msg.info.connect_req.CIP_Value = 0;
    p = msg.info.connect_req.structs;
    *p++ = 0;  // Called Party Number
    *p++ = 0;  // Calling Party Number
    *p++ = 0;  // Called Party Subaddress
    *p++ = 0;  // Calling Party Subaddress
    memcpy( p, memBProtocolRaw[nBProtocolIndex], memBProtocolRaw[nBProtocolIndex][0]+1 ); // B Protocol
    p += memBProtocolRaw[nBProtocolIndex][0]+1;
    *p++ = 0;                                         // BC
    *p++ = 0;                                         // LLC
    *p++ = 0;                                         // HLC
    *p++ = 6;                                         // Additional Info
        *p++ = 2;                                     // B Channel Information
            *p++ = 0x01;         // Use D channel (low byte)
            *p++ = 0x00;         // Use D channel (high byte)
        *p++ = 0;  // Keypad facility
        *p++ = 0;  // User-user data
        *p++ = 0;  // Facility Data Array

    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;

    if ( m_msg.header.number != 0x00 ) {
        logout( plogstream, "ERROR: more than one simultaneously initiated connection\n" );
        return;
    }
    else
        m_msg = msg;

    SubmitCAPIMessage( msg );
}

void Connection::SelectBProtocol( byte *memBProtocol )
{
    struct _API_MSG msg;
    byte *p;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _SELECT_B_REQ;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;

    p = msg.info.select_b_req.structs;
    memcpy( p, memBProtocol, memBProtocol[0]+1 ); // B Protocol
    p += memBProtocol[0]+1;

    assert( p-(byte *)&msg < sizeof(msg) );
    msg.header.length = p-(byte *)&msg;
    m_capi.SubmitCAPIMessage( msg );
}

dword Connection::SendData( word hData, byte *memData, dword nDataLen )
{
    struct _API_MSG msg;

    memset( &msg, 0x00, sizeof(msg) );
    msg.header.appl_id = (word)m_capi.wApplId;
    msg.header.command = _DATA_B3_R;
    msg.header.controller = (byte)m_capi.dwController;
    msg.header.plci = (byte)m_plci;
    msg.header.ncci = (byte)m_ncci;

    msg.info.data_b3_req.Data = (dword)memData;
    msg.info.data_b3_req.Data_Length = (word)nDataLen;
    msg.info.data_b3_req.Number = hData;
    msg.info.data_b3_req.Flags = 0;

    msg.header.length = (byte *)&msg.info.data_b3_req.Flags+2 - (byte *)&msg;
    return m_capi.SubmitCAPIMessage( msg );
}

void Connection::AdvancedSendData( byte *memData, dword nDataLen )
{
    static word hData = 0;
    dword dwRetCode;

    dwRetCode = SendData( ++hData, memData, nDataLen );
    if ( dwRetCode != 0x1103 )
        return;

    ::ResetEvent( m_capi.hDataConfEvent );

    for ( ; ; ) {
        switch ( ::WaitForSingleObject(m_capi.hDataConfEvent,3000) )
        {
            case WAIT_OBJECT_0:
                break;
            case WAIT_TIMEOUT:
                logout( m_capi.plogstream, "Something is wrong with sending data.\n" );
                return;
            default:
                assert(0);
        };

        dwRetCode = SendData( hData, memData, nDataLen );
        if ( dwRetCode == 0 )
            break;
    }
}

void CAPI::SpitToLog( const _API_MSG& msg ) const
{
    word command = msg.header.command;
    word number = msg.header.number;
    byte plci = msg.header.plci;
    byte ncci = (byte)msg.header.ncci;

    const byte *p;
    word commandtype = command & 0xFF00;

    if ( commandtype == 0x8000 || commandtype == 0x8300 ) {
        if ( command != (_DATA_B3_I | RESPONSE) && command != _DATA_B3_R )
            logout( plogstream, "<<< (0x%04X)", command );
    }
    else if ( commandtype == 0x8100 || commandtype == 0x8200 ) {
        if ( command != _DATA_B3_I && command != (_DATA_B3_R | CONFIRM) )
            logout( plogstream, ">>> (0x%04X)", command );
    }

    switch ( command )
    {
        case _LISTEN_R:
            logout( plogstream, " LISTEN_REQ, Info Mask=0x%04X, CIP Mask=0x%04X\n", msg.info.listen_req.Info_Mask, msg.info.listen_req.CIP_Mask );
            break;
        case _CONNECT_R:
            logout( plogstream, " CONNECT_REQ, CIP=0x%04X\n", msg.info.connect_req.CIP_Value );
            p = msg.info.connect_req.structs;
            logout( plogstream, "\tCalled party number: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tCalling party number: " );
            p = decodeNumber( p, 1 );
            logout( plogstream, "\tCalled party subaddress: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tCalling party subaddress: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tB protocol: " );
            p = dumpParameter( p );
            logout( plogstream, "\tBearer capability: " );
            p = dumpParameter( p );
            logout( plogstream, "\tLLC: " );
            p = dumpParameter( p );
            logout( plogstream, "\tHLC: " );
            p = dumpParameter( p );
            logout( plogstream, "\tAdditional Info: " );
            p = dumpParameter( p );
            break;
        case _ALERT_R:
            logout( plogstream, " ALERT_REQ (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            p = msg.info.alert_req.structs;
            logout( plogstream, "\tAdditional Info: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_B3_R:
            logout( plogstream, " CONNECT_B3_REQ (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            p = msg.info.connect_b3_req.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _DATA_B3_R:
            if ( const_cast<CAPI*>(this)->conns[msg.header.plci]->nBChannel == 0 )
            {
                logout( plogstream, "D-X (%02d):    ", msg.header.plci );
                for ( int i = 0; i < msg.info.data_b3_req.Data_Length; ++i )
                    logout( plogstream, " %02x", ((byte*)msg.info.data_b3_req.Data)[i] );
                logout( plogstream, "\n" );
            }
            break;
        case _RESET_B3_R:
            logout( plogstream, " RESET_B3_REQ (%d), PLCI=%02d, NCCI=%02d\n", msg.header.number, msg.header.plci, msg.header.ncci );
            p = msg.info.reset_b3_req.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _DISCONNECT_B3_R:
            logout( plogstream, " DISCONNECT_B3_REQ (%d), PLCI=%02d, NCCI=%02d\n", msg.header.number, msg.header.plci, msg.header.ncci );
            p = msg.info.disconnect_b3_req.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _DISCONNECT_R:
            logout( plogstream, " DISCONNECT_REQ (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            p = msg.info.disconnect_req.structs;
            logout( plogstream, "\tAdditional Info: " );
            p = dumpParameter( p );
            break;
        case _FACILITY_R:
            logout( plogstream, " FACILITY_REQ (%d), PLCI=%02d, NCCI=%02d, Selector=0x%04X\n", msg.header.number, msg.header.plci, msg.header.ncci, msg.info.facility_req.Selector );
            p = msg.info.facility_req.structs;
            logout( plogstream, "\tParameters: " );
            p = dumpParameter( p );
            break;
        case _INFO_R:
            logout( plogstream, " INFO_REQ (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            p = msg.info.info_req.structs;
            logout( plogstream, "\tCalled party number: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tAdditional Info: " );
            p = dumpParameter( p );
            break;
        case _MANUFACTURER_R:
            logout( plogstream, " MANUFACTURER_REQ (%d), ...\n", msg.header.number );
            break;
        case _SELECT_B_REQ:
            logout( plogstream, " SELECT_B_PROTOCOL_REQ (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            p = msg.info.select_b_req.structs;
            logout( plogstream, "\tB Protocol: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_I | RESPONSE:
            logout( plogstream, " CONNECT_RESP (%d), PLCI=%02d, Reject=0x%04X\n", msg.header.number, msg.header.plci, msg.info.connect_res.Accept );
            p = msg.info.connect_res.structs;
            logout( plogstream, "\tB Protocol: " );
            p = dumpParameter( p );
            logout( plogstream, "\tConnected Number: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tConnected Subaddress: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tLLC: " );
            p = dumpParameter( p );
            logout( plogstream, "\tAdditional Info: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_ACTIVE_I | RESPONSE:
            logout( plogstream, " CONNECT_ACTIVE_RESP (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            break;
        case _CONNECT_B3_I | RESPONSE:
            logout( plogstream, " CONNECT_B3_RESP (%d), PLCI=%02d, NCCI=%02d, Reject=0x%04X\n", msg.header.number, msg.header.plci, msg.header.ncci, msg.info.connect_b3_res.Accept );
            p = msg.info.connect_b3_res.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_B3_ACTIVE_I | RESPONSE:
            logout( plogstream, " CONNECT_B3_ACTIVE_RESP (%d), PLCI=%02d, NCCI=%02d\n", msg.header.number, msg.header.plci, msg.header.ncci );
            break;
        case _CONNECT_B3_T90_ACTIVE_I | RESPONSE:
            logout( plogstream, " CONNECT_B3_T90_ACTIVE_RESP (%d), PLCI=%02d, NCCI=%02d\n", msg.header.number, msg.header.plci, msg.header.ncci );
            break;
        case _DATA_B3_I | RESPONSE:
//            logout( plogstream, " DATA_B3_RESP (%d), PLCI=%02d, NCCI=%02d, Data Handle=0x%04X\n", msg.header.number, msg.header.plci, msg.header.ncci, msg.info.data_b3_res.Number );
            break;
        case _RESET_B3_I | RESPONSE:
            logout( plogstream, " RESET_B3_RESP (%d), PLCI=%02d, NCCI=%02d\n", msg.header.number, msg.header.plci, msg.header.ncci );
            break;
        case _DISCONNECT_B3_I | RESPONSE:
            logout( plogstream, " DISCONNECT_B3_RESP (%d), PLCI=%02d, NCCI=%02d\n", msg.header.number, msg.header.plci, msg.header.ncci );
            break;
        case _DISCONNECT_I | RESPONSE:
            logout( plogstream, " DISCONNECT_RESP (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            break;
        case _FACILITY_I | RESPONSE:
            logout( plogstream, " FACILITY_RESP (%d), PLCI=%02d, Selector=0x%04X\n", msg.header.number, msg.header.plci, msg.info.facility_res.Selector );
            p = msg.info.facility_res.structs;
            logout( plogstream, "\tParameters: " );
            p = dumpParameter( p );
            break;
        case _INFO_I | RESPONSE:
            logout( plogstream, " INFO_RESP (%d), PLCI=%02d\n", msg.header.number, msg.header.plci );
            break;
        case _MANUFACTURER_I | RESPONSE:
            logout( plogstream, " MANUFACTURER_RESP (%d)\n", msg.header.number );
            break;
        case _LISTEN_R | CONFIRM:
            logout( plogstream, " LISTEN_CONF (%d), Info=0x%04X\n", number, msg.info.listen_con.Info );
            break;
        case _CONNECT_R | CONFIRM:
            logout( plogstream, " CONNECT_CONF (%d), PLCI=%02d, Info=0x%04X\n", number, plci, msg.info.connect_con.Info );
            break;
        case _ALERT_R | CONFIRM:
            logout( plogstream, " ALERT_CONF (%d), PLCI=%02d, Info=0x%04X\n", number, plci, msg.info.alert_con.Info );
            break;
        case _CONNECT_B3_R | CONFIRM:
            logout( plogstream, " CONNECT_B3_CONF (%d), NCCI=%02d, Info=0x%04X\n", number, ncci, msg.info.connect_b3_con.Info );
            break;
        case _DATA_B3_R | CONFIRM:
//            logout( plogstream, " DATA_B3_CONF (%d), msg->header.number, PLCI=%02d, NCCI=%02d, Data Handle=0x%04X, Info=0x%04X\n", number, plci, ncci, msg.info.data_b3_con.Number, msg.info.data_b3_con.Info );
            break;
        case _RESET_B3_R | CONFIRM:
            logout( plogstream, " RESET_B3_CONF (%d), NCCI=%02d, Info=0x%04X\n", number, ncci, msg.info.reset_b3_con.Info );
            break;
        case _DISCONNECT_B3_R | CONFIRM:
            logout( plogstream, " DISCONNECT_B3_CONF (%d), NCCI=%02d, Info=0x%04X\n", number, ncci, msg.info.disconnect_b3_con.Info );
            break;
        case _DISCONNECT_R | CONFIRM:
            logout( plogstream, " DISCONNECT_CONF (%d), PLCI=%02d, Info=0x%04X\n", number, plci, msg.info.disconnect_con.Info );
            break;
        case _FACILITY_R | CONFIRM:
            logout( plogstream, " FACILITY_CONF (%d), PLCI=%02d, Info=0x%04X, Selector=0x%04X\n", number, plci, msg.info.facility_con.Info, msg.info.facility_con.Selector );
            p = msg.info.facility_con.structs;
            logout( plogstream, "\tParameters: " );
            p = dumpParameter( p );
            break;
        case _INFO_R | CONFIRM:
            logout( plogstream, " INFO_CONF (%d), PLCI=%02d, Info=0x%04X\n", number, plci, msg.info.info_con.Info );
            break;
        case _MANUFACTURER_R | CONFIRM:
            logout( plogstream, " MANUFACTURER_CONF (%d), ...\n", number );
            break;
        case _SELECT_B_REQ | CONFIRM:
            logout( plogstream, " SELECT_B_PROTOCOL_CONF (%d), PLCI=%02d, Info=0x%04X\n", number, plci, msg.info.select_b_con.Info );
            break;
        case _CONNECT_I:
            logout( plogstream, " CONNECT_IND (%d), PLCI=%02d, CIP=0x%04X\n", number, plci, msg.info.connect_ind.CIP_Value );
            p = msg.info.connect_ind.structs;
            logout( plogstream, "\tCalled party number: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tCalling party number: " );
            p = decodeNumber( p, 1 );
            logout( plogstream, "\tCalled party subaddress: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tCalling party subaddress: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tBearer capability: " );
            p = dumpParameter( p );
            logout( plogstream, "\tLLC: " );
            p = dumpParameter( p );
            logout( plogstream, "\tHLC: " );
            p = dumpParameter( p );
            logout( plogstream, "\tAdditional Info: " );
            p = dumpParameter( p );
            logout( plogstream, "\tSecond calling party number: " );
            p = dumpParameter( p ); // p = decodeNumber( p, 1 );
            break;
        case _CONNECT_ACTIVE_I:
            logout( plogstream, " CONNECT_ACTIVE_IND (%d), PLCI=%02d\n", number, plci );
            p = msg.info.connect_a_ind.structs;
            logout( plogstream, "\tConnected number: " );
            p = decodeNumber( p, 1 );
            logout( plogstream, "\tConnected subaddress: " );
            p = decodeNumber( p, 0 );
            logout( plogstream, "\tLLC: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_B3_I:
            logout( plogstream, " CONNECT_B3_IND (%d), PLCI=%02d, NCCI=%02d\n", number, plci, ncci );
            p = msg.info.connect_b3_ind.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_B3_ACTIVE_I:
            logout( plogstream, " CONNECT_B3_ACTIVE_IND (%d), PLCI=%02d, NCCI=%02d\n", number, plci, ncci );
            p = msg.info.connect_b3_a_ind.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _CONNECT_B3_T90_ACTIVE_I:
            logout( plogstream, " CONNECT_B3_T90_ACTIVE_IND (%d), PLCI=%02d, NCCI=%02d\n", number, plci, ncci );
            p = msg.info.connect_b3_t90_a_ind.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _DATA_B3_I:
            if ( const_cast<CAPI*>(this)->conns[msg.header.plci]->nBChannel == 0 )
            {
                logout( plogstream, "D-R (%02d):", msg.header.plci );
                for ( int i = 0; i < msg.info.data_b3_ind.Data_Length; ++i )
                    logout( plogstream, " %02x", ((byte*)msg.info.data_b3_ind.Data)[i] );
                logout( plogstream, "\n" );
            }
            break;
        case _RESET_B3_I:
            logout( plogstream, " RESET_B3_IND (%d), PLCI=%02d, NCCI=%02d\n", number, plci, ncci );
            p = msg.info.reset_b3_ind.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _DISCONNECT_B3_I:
            logout( plogstream, " DISCONNECT_B3_IND (%d), PLCI=%02d, NCCI=%02d, ReasonB3=0x%04X\n", number, plci, ncci, msg.info.disconnect_b3_ind.Info );
            p = msg.info.disconnect_b3_ind.structs;
            logout( plogstream, "\tNCPI: " );
            p = dumpParameter( p );
            break;
        case _DISCONNECT_I:
            logout( plogstream, " DISCONNECT_IND (%d), PLCI=%02d, Reason=0x%04X\n", number, plci, msg.info.disconnect_ind.Info );
            break;
        case _FACILITY_I:
            logout( plogstream, " FACILITY_IND (%d), PLCI=%02d, Selector=0x%04X\n", number, plci, msg.info.facility_ind.Selector );
            p = msg.info.facility_ind.structs;
            logout( plogstream, "\tParameters: " );
            p = dumpParameter( p );
            break;
        case _INFO_I:
            logout( plogstream, " INFO_IND (%d), PLCI=%02d, Info number=0x%04X\n", number, plci, msg.info.info_ind.Number );
            p = msg.info.info_ind.structs;
            logout( plogstream, "\tInfo element: " );
            p = dumpParameter( p );
            break;
        default:
            break;
    }

    if ( commandtype == 0x8100 && command != (_DATA_B3_R | CONFIRM) )
        logout( plogstream, "\n" );

    if ( commandtype == 0x8300 && command != (_DATA_B3_I | RESPONSE) )
        logout( plogstream, "\n" );
    
    if ( command == (_DISCONNECT_I | RESPONSE) )
        logout( plogstream, "**************\n\n" );
}

//*********************** NOT USED, FOR FUTURE *****************************//
//*********************** NOT USED, FOR FUTURE *****************************//
//*********************** NOT USED, FOR FUTURE *****************************//
//*********************** NOT USED, FOR FUTURE *****************************//
//*********************** NOT USED, FOR FUTURE *****************************//
//*********************** NOT USED, FOR FUTURE *****************************//
//*********************** NOT USED, FOR FUTURE *****************************//
/*
//==========================================================================>>
// Logging
//==========================================================================>>

#define LOG_EMERG        0       // System is unusable
#define LOG_ALERT        2       // Action must be taken immediately
#define LOG_CRIT         4       // Critical conditions
#define LOG_ERR          6       // Error conditions
#define LOG_WARNING      8       // Warning conditions
#define LOG_NOTICE      10       // Normal but significant condition
#define LOG_INFO        12       // Informational
#define LOG_DEBUG       14       // Debug-level messages

void Log( unsigned long dwLogLevel, const char *szFormat, ... )
{
    va_list args;
    char szBuf[2048];
    long len;

    va_start( args, szFormat );
    len = _vsnprintf( szBuf, sizeof(szBuf), szFormat, args );
    va_end(args);

    if ( len < 0 || len > sizeof(szBuf)-1 )
        len = sizeof(szBuf)-1;

    szBuf[len] = '\0';

    ::OutputDebugString(szBuf);
}

//==========================================================================>>
// WaveSession
//==========================================================================>>

class WaveSession // Not configurable: only PCM, 8kHz, 16-bit, Mono
{
private:
    WaveSession( WaveSession& );
    const WaveSession& operator=( WaveSession& );

public:
    bool operator !() { return !IsActive(); }
    WaveSession() { Open(); }
    ~WaveSession() { Close(); }

private:
    HWAVEOUT hWaveOut;
    MMRESULT mmresult;

    bool IsActive() { return hWaveOut != 0; }

    bool Open();
    void Close();
};

bool WaveSession::Open()
{
    ::Log( LOG_DEBUG, "Check whether the WaveSession is already active");
    if ( IsActive() ) {
        ::Log( LOG_ERR, "The WaveSession is already active" );
        return false;
    }

    WAVEFORMATEX wfex;

    wfex.wFormatTag = WAVE_FORMAT_PCM;
    wfex.nChannels = 1;
    wfex.nSamplesPerSec = 8000;
    wfex.wBitsPerSample = 16;
    wfex.nBlockAlign = (wfex.wBitsPerSample*wfex.nChannels)>>3;
    wfex.nAvgBytesPerSec = wfex.nSamplesPerSec*wfex.nBlockAlign;

    ::Log( LOG_DEBUG, "waveOutOpen()" );
    mmresult = ::waveOutOpen( &hWaveOut, WAVE_MAPPER, &wfex, 0, 0, WAVE_FORMAT_DIRECT );

    if ( mmresult != MMSYSERR_NOERROR ) {
        hWaveOut = 0;
        ::Log( LOG_ERR, "waveOutOpen failed, mmresult=0x%08x", mmresult );
        return false;
    }

    ::Log( LOG_DEBUG, "waveOutOpen success" );
    return true;
}

void WaveSession::Close()
{
    if ( !IsActive() ) {
        ::Log( LOG_DEBUG, "Not closing the WaveSession because it is not active");
        return;
    }

    ::Log( LOG_DEBUG, "waveOutReset()" );
    mmresult = ::waveOutReset( hWaveOut );
    if ( mmresult != MMSYSERR_NOERROR )
        ::Log( LOG_ERR, "waveOutReset failed, mmresult=0x%08x", mmresult );
    else
        ::Log( LOG_DEBUG, "waveOutReset success" );

    ::Log( LOG_DEBUG, "waveOutClose()" );
    mmresult = ::waveOutClose( hWaveOut );
    if ( mmresult != MMSYSERR_NOERROR )
        ::Log( LOG_ERR, "waveOutClose failed, mmresult=0x%08x", mmresult );
    else
        ::Log( LOG_DEBUG, "waveOutClose success" );
}

//==========================================================================>>
// SignalWaitingThread
//==========================================================================>>

DWORD CALLBACK Capi::SignalWaitingThread( void *pCapi )
{
    Capi *pthis = static_cast<Capi*>( pCapi );
    const HANDLE ObjectsToWait[] = { pthis->hTerminateEvent, hStartSignalWaitingEvent };

    for ( ; ; )
    {
        DWORD dwResult = WaitForMultipleObjects( sizeof(ObjectsToWait), ObjectsToWait, 0, INFINITE );

        if ( dwResult == WAIT_OBJECT_0 )
            break;

        if ( dwResult != WAIT_OBJECT_1 ) {
            ::Log( LOG_ERR, "SignalWaitingThread: unexpected result from WaitForMultipleObjects: 0x%08x", dwResult );
            break;
        }

        dword dwResult = CAPI_WAIT_FOR_SIGNAL( pthis->wApplId );

        if ( dwResult == 0x1101 ) { // Invalid application number
            ::Log( LOG_INFO, "SignalWaitingThread: looks like we have terminated..." );
            break;
        }
        else if ( dwResult != 0 ) {
            ::Log( LOG_INFO, "SignalWaitingThread: CAPI_WAIT_FOR_SIGNAL failed, error=0x%04x", dwResult );
            continue;
        }

        if ( !::ResetEvent( hStartSignalWaitingEvent ) )
            ::Log( LOG_ERR, "SignalWaitingThread: error resetting hStartSignalWaitingEvent: 0x%08x", GetLastError() );

        if ( !::SetEvent( hSignalEvent ) )
            ::Log( LOG_ERR, "SignalWaitingThread: error signaling hSignalEvent: 0x%08x", GetLastError() );
    }

    return 0;
}

//==========================================================================>>
// Player
//==========================================================================>>

class Prompt
{
private:
    Prompt( Prompt& );
    const Prompt& operator=( Prompt& );

public:
    Prompt( const string& sFileName );
    Prompt( char *data, unsigned long dwDataLen );
    ~Prompt();

    const char *GetData() const { return data; }
    unsigned long GetDataLen() const { return dwDataLen; }

private:
    char *data;
    unsigned long dwDataLen;
    bool bDataOwner;
};

Prompt::Prompt( const string& sFileName )
    data(0),
    dwDataLen(0),
    bDataOwner( true )
{
    _stat st;

    int result = _stat( sFileName.c_str(), &st );
    if ( result != 0 ) {
        ::Log( LOG_ERR, "_stat(%s) failed, errno=%d", sFileName.c_str(), errno );
        return;
    }

    data = new char[st.st_size];

    if ( data == 0 ) {
        ::Log( LOG_ERR, "Cannot allocate buffer of size %d to hold the file %s", st.st_size, sFileName.c_str() );
        return;
    }

    dwDataLen = st.st_size;

    ifstream is( sFileName.c_str(), ios::in | ios::binary );
    if ( !is ) {
        ::Log( LOG_ERR, "Cannot open file %s", sFileName.c_str() );
        delete[] data;
        data = 0;
        dwDataLen = 0;
        return;
    }

    is.read( data, dwDataLen );

    if ( is.gcount() != dwDataLen || !is ) {
        ::Log( LOG_ERR, "Error reading file %s", sFileName.c_str() );
        delete[] data;
        data = 0;
        dwDataLen = 0;
        return;
    }
}

Prompt::Prompt( char *data, unsigned long dwDataLen )
    data( data ),
    dwDataLen( dwDataLen ),
    bDataOwner( false )
{
}

Prompt::~Prompt()
{
    if ( bDataOwner )
        delete[] data;
}

void PlayPrompt( const Prompt& prompt )
{
    for ( i = prompt.GetDataLen()/CAPI::maxBDataLen, p = prompt.GetData(); i > 0; i--, p+=CAPI::maxBDataLen )
        CAPI::AdvancedSendData( plci, p, CAPI::maxBDataLen );

    if ( nSize%CAPI::maxBDataLen != 0 )
        CAPI::AdvancedSendData( plci, p, nSize%CAPI::maxBDataLen );

}

char Menu( const Prompt& prompt, const string& sValidChoice )
{
    for ( i = nSize/CAPI::maxBDataLen, p = buf; i > 0; i--, p+=CAPI::maxBDataLen )
        CAPI::AdvancedSendData( plci, p, CAPI::maxBDataLen );

    if ( nSize%CAPI::maxBDataLen != 0 )
        CAPI::AdvancedSendData( plci, p, nSize%CAPI::maxBDataLen );

    break;
}
*/

